use std::sync::Arc;

use tokio::sync::Mutex;
use tracing::{debug, error, info, trace, warn};

use bc_base::{TransactionValue, transaction::Transaction, wallet::wallet_cache::WalletCache};
use bc_consensus::Consensus;

use thiserror::Error;

use bc_base::transaction::TransactionError;

// Reexport the structs
pub use wallet::Wallet;

mod wallet;
mod wallet_storage_with_cache;

#[derive(Error, Debug, PartialEq)]
pub enum WalletsError {
    #[error("bc_base wallet error: {0}")]
    BcBaseWalletError(#[from] bc_base::wallet::WalletBaseError),

    #[error("utxo not exists")]
    UtxoNotExists,

    #[error("Insufficient balance")]
    InsufficientBalance,

    #[error("BlockDataStorageError error: {0}")]
    BlockDataStorageError(#[from] bc_block_data::BlockDataStorageError),

    #[error("TransactionError error: {0}")]
    TransactionError(#[from] TransactionError),
}

// int64 GetCredit(bool fUseCache = false) const
async fn get_credit(
    tx: &Transaction,
    wallet_cache: &WalletCache,
    consensus: Arc<Mutex<Consensus>>,
) -> TransactionValue {
    // TODO: cache credit
    let tx_id = tx.calc_tx_id();
    trace!("Get credit for ({})", tx_id);

    if tx.is_coinbase() {
        let is_mature = consensus.lock().await.is_coinbase_mature(&tx_id).await;
        match is_mature {
            Ok(true) => {
                trace!("coinbase is mature.");
            }
            Ok(false) => {
                info!("coinbase({}) has not matured.", &tx_id);
                return 0;
            }
            Err(e) => {
                warn!(
                    "An error({}) occurred when determining whether tx({}) has already matured.",
                    e, tx_id
                );
                return 0;
            }
        }
    }

    let mut val = 0;
    debug!("outputs len: {}", tx.get_outputs().len());
    for output in tx.get_outputs() {
        let pub_key_hash = output.get_payee_pubkey_hash().unwrap();
        trace!("pub_key_hash ({}).", pub_key_hash);
        if wallet_cache.is_mine(&pub_key_hash) {
            debug!(
                "This is my pub_key_hash ({}), the value is {}.",
                pub_key_hash,
                output.get_value()
            );
            val += output.get_value();
        }
    }

    val
}

async fn get_balance(
    consensus: Arc<Mutex<Consensus>>,
    wallet_cache: &WalletCache,
) -> TransactionValue {
    let wallet_txs = wallet_cache.get_wallet_txs();
    debug!("[get balance] wallet txs len: {}", wallet_txs.len());
    let mut balance = 0;
    for (tx_id, wallet_tx) in wallet_txs {
        trace!(
            "wallet tx: {}, is spent: {}, is final: {}",
            tx_id,
            wallet_tx.is_spent(),
            wallet_tx.is_final(0, 0)
        );
        if !wallet_tx.is_spent() && wallet_tx.is_final(0, 0) {
            balance += get_credit(wallet_tx.get_tx(), wallet_cache, consensus.clone()).await;
        }
    }

    balance
}
